import { idx2RCB, RCB2Idx, excludeBit, isPowerOf2 } from "../utils";
import { CellProperty } from "../types";
import {
  callbackChecking,
  callbackConfirmed,
  callbackCheckingArea,
} from "../callbacks";
import uniqueArray from "./uniqueArray";

class Solver {
  private puzzle: number[];
  private unSolvedCells: number[];
  private Vector: number[];

  constructor(puzzle: number[]) {
    this.puzzle = puzzle;
    this.unSolvedCells = [];
    this.Vector = new Array(81).fill(0x1ff);
    for (let i = 0; i < 81; i++) {
      if (puzzle[i] === 0) {
        this.unSolvedCells.push(i);
      } else {
        this.excludeNum(i, puzzle[i]);
      }
    }
  }

  private excludeNum(idx: number, num: number): void {
    const [row, col, block] = idx2RCB(idx);
    const rowIdxs = RCB2Idx(row, CellProperty.row);
    const colIdxs = RCB2Idx(col, CellProperty.col);
    const blockIdxs = RCB2Idx(block, CellProperty.block);
    const idxs = [...rowIdxs, ...colIdxs, ...blockIdxs].filter(
      (i) => i !== idx
    );
    this.Vector = excludeBit(num, this.Vector, idxs);
    this.Vector[idx] = 1 << (num - 1);
  }

  public showVector(): void {
    for (let i = 0; i < 9; i++) {
      const row = this.Vector.slice(i * 9, (i + 1) * 9);
      const str = row
        .map((n) => n.toString(2).padStart(9, "0").split("").reverse().join(""))
        .join(",");
      console.log(str);
    }
  }

  private getMaskByProperty(idx: number, property: CellProperty): number {
    const p = idx2RCB(idx);
    const idxsToCheck = RCB2Idx(p[property], property).filter((i) => i !== idx);
    callbackCheckingArea(this, idxsToCheck);
    //allCandidates表示该行或列或块中所有疑似数字的并集
    const allCandidates = idxsToCheck.reduce(
      (acc, cur) => acc | this.Vector[cur],
      0
    );
    return this.Vector[idx] & ~allCandidates;
  }

  public showMask() {
    let str = "";
    for (let i = 0; i < 9; i++) {
      for (let j = 0; j < 9; j++) {
        const masks = [0, 1, 2].map((p) =>
          this.getMaskByProperty(i * 9 + j, p)
        );
        str +=
          "[" +
          masks
            .map((n) =>
              n.toString(2).padStart(9, "0").split("").reverse().join("")
            )
            .join(",") +
          "], ";
      }
      str += "\n";
    }
    console.log(str);
  }

  private handleCellConfirmed(
    idx: number,
    vector: number,
    influencedCells: uniqueArray
  ): void {
    callbackConfirmed(this, idx);
    this.puzzle[idx] = Math.log2(vector) + 1;
    this.unSolvedCells = this.unSolvedCells.filter((i) => i !== idx);
    this.excludeNum(idx, this.puzzle[idx]);
    const [row, col, block] = idx2RCB(idx);
    const rowIdxs = RCB2Idx(row, CellProperty.row);
    const colIdxs = RCB2Idx(col, CellProperty.col);
    const blockIdxs = RCB2Idx(block, CellProperty.block);
    [...rowIdxs, ...colIdxs, ...blockIdxs].forEach((i) => {
      //注意不要将自己或者已经确定的格子加入
      if (i === idx || this.puzzle[i] !== 0) return;
      influencedCells.push(i);
    });
  }

  //逻辑求解，返回是否有解，为了减少后续的回溯次数，这里只做单步的逻辑求解
  private logicSolve(): boolean {
    const influencedCells = new uniqueArray();
    //初始化influencedCells，将所有未解决的格子加入
    this.unSolvedCells.forEach((idx) => influencedCells.push(idx));
    while (!influencedCells.isEmpty()) {
      const idx = influencedCells.pop();
      if (this.Vector[idx] === 0) continue;
      callbackChecking(this, idx);
      if (isPowerOf2(this.Vector[idx])) {
        this.handleCellConfirmed(idx, this.Vector[idx], influencedCells);
      }
      for (let i = 0; i < 3; i++) {
        const mask = this.getMaskByProperty(idx, i);
        if (mask === 0) continue;
        if (!isPowerOf2(mask)) {
          throw new Error(
            `mask error, expect 0 or power of 2, got ${mask}, puzzle has no solution`
          );
        }
        this.handleCellConfirmed(idx, mask, influencedCells);
      }
    }
    return this.unSolvedCells.length === 0;
  }

  //验证当前答案是否合法
  private verify(): boolean {
    for (let i = 0; i < 9; i++) {
      const P = new Array(3).fill(0);
      for (let j = 0; j < 3; j++) {
        const idxs = RCB2Idx(i, j);
        for (const idx of idxs) {
          if (this.puzzle[idx] === 0) continue;
          if (P[j] & (1 << (this.puzzle[idx] - 1))) {
            console.log(
              `verify failed at idx: ${idx}, value: ${this.puzzle[idx]}`
            );
            return false;
          }
          P[j] |= 1 << (this.puzzle[idx] - 1);
        }
      }
    }
    return true;
  }
  public solve(): boolean {
    return this.logicSolve();
  }

  public get table(): number[] {
    return this.puzzle;
  }
}

export default Solver;
